· 6 min read
We could have stored pages in a database and rendered them from JSON. We chose files you can open in your editor, and it has paid for itself every week since.
Marcus Bell
Co-founder and CTO
The first version of Starter kept pages in a database. Every section was a row, every field a JSON blob, and a renderer stitched them together on each request. It demoed well. It also meant that the moment a developer wanted to change a section, they had to leave their editor, open ours, and work through a form that knew less about HTML than they did.
We threw it out in the spring of 2025. Since then, every section in Starter is an ordinary anonymous Blade component with a small YAML file beside it. The page you edit on the canvas is a file in resources/views/pages. The canvas reads it, and when you publish, it writes it back.
A section is two files. The Blade file declares its props with defaults, and renders them. The YAML file names the same props, says what kind of field each one is, and repeats the defaults. That is the whole contract.
@props(['heading' => 'Plans that grow with you'])
<h2 class="text-h2">{{ $heading }}</h2>
Starter does not annotate anything. It reads the YAML to build the inspector, and it reads the Blade to find where each field is echoed, so clicking the heading on the canvas puts a caret in the right place. If you write a new section by hand and give it a YAML file, it shows up in the Add Section picker on the next page load.
Three things decided it for us, and none of them were about performance.
@php blocks and loops. Tailwind sees the classes. Your formatter formats them. Your deploy deploys them. We did not have to build a single one of those things.Files are harder for us than rows. The writer that turns an edit back into a page file has to change one attribute and leave every other byte alone, including your comments and your blank lines. It took Owen most of last winter to make that true across all forty templates we test against, and the test that guards it still runs on every commit: install a template, write every page back without changes, and fail if a single byte moved.
The other cost is that some things a database makes easy, like full-text search across pages, are slower to build. We decided that was a fine trade for a product whose job is a marketing site of a few dozen pages.
In practice, the split is clean. Developers own the sections: their markup, their props, which fields exist. Editors own the values: the words, the images, the order of sections on a page. Neither waits on the other, and both of their changes land in the same repository.
If you have a section library already, the fastest way to try this is to add a YAML file next to one component you like and open the page in Starter. You will see its fields in the inspector in about the time it took to read this paragraph.
What shipped, what changed, and one marketing page worth stealing from another team. That is all.
No spam. Unsubscribe any time.